package com.c2b.algorithm.leetcode.base;

import java.util.HashMap;
import java.util.Map;

/**
 * <a href='https://leetcode.cn/problems/lfu-cache/description/'>LFU 缓存(LFU Cache)</a>
 * <p>请你为<a href='https://baike.baidu.com/item/%E7%BC%93%E5%AD%98%E7%AE%97%E6%B3%95'>最不经常使用（LFU）</a> 缓存算法设计并实现数据结构。</p>
 * <p>
 * 实现 LFUCache 类：
 *     <ul>
 *         <li>LFUCache(int capacity) - 用数据结构的容量 capacity 初始化对象</li>
 *         <li>int get(int key) - 如果键 key 存在于缓存中，则获取键的值，否则返回 -1 。</li>
 *         <li>void put(int key, int value) - 如果键 key 已存在，则变更其值；如果键不存在，请插入键值对。当缓存达到其容量 capacity 时，则应该在插入新项之前，移除最不经常使用的项。在此问题中，当存在平局（即两个或更多个键具有相同使用频率）时，应该去除 最久未使用 的键。</li>
 *     </ul>
 *     为了确定最不常使用的键，可以为缓存中的每个键维护一个 使用计数器 。使用计数最小的键是最久未使用的键。<br>
 *     当一个键首次插入到缓存中时，它的使用计数器被设置为 1 (由于 put 操作)。对缓存中的键执行 get 或 put 操作，使用计数器的值将会递增。
 * </p>
 * <p>函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。</p>
 *
 * <p>
 * <b>示例</b>
 * <pre>
 * 示例：
 *      输入：
 *          ["LFUCache", "put", "put", "get", "put", "get", "get", "put", "get", "get", "get"]
 *          [[2], [1, 1], [2, 2], [1], [3, 3], [2], [3], [4, 4], [1], [3], [4]]
 *      输出：
 *          [null, null, null, 1, null, -1, 3, null, -1, 3, 4]
 *      解释：
 *          // cnt(x) = 键 x 的使用计数
 *          // cache=[] 将显示最后一次使用的顺序（最左边的元素是最近的）
 *          LFUCache lfu = new LFUCache(2);
 *          lfu.put(1, 1);   // cache=[1,_], cnt(1)=1
 *          lfu.put(2, 2);   // cache=[2,1], cnt(2)=1, cnt(1)=1
 *          lfu.get(1);      // 返回 1
 *                           // cache=[1,2], cnt(2)=1, cnt(1)=2
 *          lfu.put(3, 3);   // 去除键 2 ，因为 cnt(2)=1 ，使用计数最小
 *                           // cache=[3,1], cnt(3)=1, cnt(1)=2
 *          lfu.get(2);      // 返回 -1（未找到）
 *          lfu.get(3);      // 返回 3
 *                           // cache=[3,1], cnt(3)=2, cnt(1)=2
 *          lfu.put(4, 4);   // 去除键 1 ，1 和 3 的 cnt 相同，但 1 最久未使用
 *                           // cache=[4,3], cnt(4)=1, cnt(3)=2
 *          lfu.get(1);      // 返回 -1（未找到）
 *          lfu.get(3);      // 返回 3
 *                           // cache=[3,4], cnt(4)=1, cnt(3)=3
 *          lfu.get(4);      // 返回 4
 *                           // cache=[3,4], cnt(4)=2, cnt(3)=3
 * </pre>
 * </p>
 * <p>
 * <b>提示：</b>
 *     <ul>
 *          <li>1 <= capacity <= 10^4</li>
 *          <li>0 <= key <= 10^5</li>
 *          <li>0 <= value <= 10^9</li>
 *          <li>最多调用 2 * 10^5 次 get 和 put 方法</li>
 *     </ul>
 * </p>
 *
 * @author c2b
 * @see LC0146LRUCache_M LRU 缓存(LRU Cache)
 * @see LC0460LFUCache_H LFU 缓存(LFU Cache)
 * @since 2023/12/12 15:23
 */
public class LC0460LFUCache_H {
    /**
     * DoublyLinkedList：代表元素的访问频次。相同频次中使用 Node 组成的双向链表，代表访问的时间
     */
    static class LFUCache {
        Map<Integer, Node> cache;
        /**
         * firstLinkedList.next 是频次最大的双向链表
         */
        DoublyLinkedList firstLinkedList;
        /**
         * firstLinkedList.prev 是频次最小的双向链表
         */
        DoublyLinkedList lastLinkedList;
        int capacity;
        int size;

        public LFUCache(int capacity) {
            this.capacity = capacity;
            this.cache = new HashMap<>(capacity);
            size = 0;
            firstLinkedList = new DoublyLinkedList(-1);
            lastLinkedList = new DoublyLinkedList(-1);
            firstLinkedList.next = lastLinkedList;
            lastLinkedList.prev = firstLinkedList;
        }

        public int get(int key) {
            Node node = cache.get(key);
            if (node == null) {
                return -1;
            }
            // 将该节点的频次 +1
            freqInc(node);
            return node.val;
        }

        public void put(int key, int value) {
            if (capacity == 0) {
                return;
            }
            // 若key存在，则更新value，访问频次+1
            Node node = cache.get(key);
            if (node != null) {
                node.val = value;
                freqInc(node);
            } else {
                // 若key不存在
                if (capacity == size) {
                    // 如果缓存满了，删除lastLinkedList.pre这个链表（即表示最小频次的链表）中的tail.pre这个Node（即最小频次链表中最先访问的Node），如果该链表中的元素删空了，则删掉该链表。
                    cache.remove(lastLinkedList.prev.dummyTail.prev.key);
                    lastLinkedList.prev.removeNode(lastLinkedList.prev.dummyTail.prev);
                    size--;
                    if (lastLinkedList.prev.dummyHead.next == lastLinkedList.prev.dummyTail) {
                        removeDoublyLinkedList(lastLinkedList.prev);
                    }
                }
                // cache 中 put 新 key-Node 对，并将新node加入表示freq为1的DoublyLinkedList中，若不存在freq为1的DoublyLinkedList则新建。
                Node newNode = new Node(key, value);
                cache.put(key, newNode);
                if (lastLinkedList.prev.freq != 1) {
                    DoublyLinkedList newDoublyLinkedList = new DoublyLinkedList(1);
                    addNewListAfterTargetList(newDoublyLinkedList, lastLinkedList.prev);
                    newDoublyLinkedList.addNodeToHead(newNode);
                } else {
                    lastLinkedList.prev.addNodeToHead(newNode);
                }
                size++;
            }
        }

        /**
         * 将当前节点的频次+1。
         * <ol>
         *     <li>将当前节点从原 freq 对应的双向链表中移除，如果链表空了则删除链表</li>
         *     <li>当前节点的频次 +1</li>
         *     <li>将 node 加入到新的 freq 对应的双向链表中，若该链表不存在，则需要新建该链表</li>
         * </ol>
         */
        private void freqInc(Node node) {
            // 1.将当前节点从原 freq 对应的双向链表中移除，如果链表空了则删除链表
            DoublyLinkedList sourceDoublyLinkedList = node.doublyLinkedList;
            // 记录当前节点频次所在双向链表的前一个链表。prevDoublyLinkedList.freq>currDoublyLinkedList.freq
            DoublyLinkedList prevDoublyLinkedList = sourceDoublyLinkedList.prev;
            sourceDoublyLinkedList.removeNode(node);
            // if成立说明链表为空
            if (sourceDoublyLinkedList.dummyHead.next == sourceDoublyLinkedList.dummyTail) {
                removeDoublyLinkedList(sourceDoublyLinkedList);
            }

            // 2.当前节点的频次 +1
            node.freq++;
            // 3.将 node 加入到新的 freq 对应的双向链表中，若该链表不存在，则需要新建该链表
            if (prevDoublyLinkedList.freq == node.freq) {
                // 将当前节点加入到头部
                prevDoublyLinkedList.addNodeToHead(node);
            } else {
                // 创建新的链表
                DoublyLinkedList doublyLinkedList = new DoublyLinkedList(node.freq);
                addNewListAfterTargetList(doublyLinkedList, prevDoublyLinkedList);
                // 将当前链表添加到双向链表的链表中
                doublyLinkedList.addNodeToHead(node);
            }

        }

        /**
         * 将代表某频次的双向链表从双向链表链中移除
         */
        private void removeDoublyLinkedList(DoublyLinkedList doublyLinkedList) {
            doublyLinkedList.prev.next = doublyLinkedList.next;
            doublyLinkedList.next.prev = doublyLinkedList.prev;
        }

        /**
         * 将代表某频次的双向链表添加到双向链表链中
         */
        private void addNewListAfterTargetList(DoublyLinkedList newDoublyLinkedList, DoublyLinkedList targetDoublyLinkedList) {
            newDoublyLinkedList.prev = targetDoublyLinkedList;
            newDoublyLinkedList.next = targetDoublyLinkedList.next;
            targetDoublyLinkedList.next.prev = newDoublyLinkedList;
            targetDoublyLinkedList.next = newDoublyLinkedList;
        }

        static class Node {
            public int key;
            public int val;
            public int freq = 1;
            public Node prev;
            public Node next;
            /**
             * 当前节点频次所在的双向链表
             */
            DoublyLinkedList doublyLinkedList;

            public Node() {
            }

            public Node(int key, int val) {
                this.key = key;
                this.val = val;
            }
        }

        static class DoublyLinkedList {
            /**
             * 双向链表表示的的的频次
             */
            int freq;
            /**
             * 双向链表的前驱链表：prev.freq < curr.freq
             */
            DoublyLinkedList prev;
            /**
             * 双向链表的后继链表：next.freq > curr.freq
             */
            DoublyLinkedList next;
            /**
             * 该双向链表的虚拟头节点，新节点从头部访问，表示最近访问
             */
            Node dummyHead;
            /**
             * 该双向链表的虚拟尾节点，移除节点从尾部访问，表示最久访问
             */
            Node dummyTail;

            public DoublyLinkedList(int freq) {
                this.freq = freq;
                dummyHead = new Node();
                dummyTail = new Node();
                dummyHead.next = dummyTail;
                dummyTail.prev = dummyHead;
            }

            void removeNode(Node node) {
                node.prev.next = node.next;
                node.next.prev = node.prev;
            }

            void addNodeToHead(Node node) {
                node.prev = dummyHead;
                node.next = dummyHead.next;
                dummyHead.next.prev = node;
                dummyHead.next = node;
                node.doublyLinkedList = this;
            }
        }
    }


    public static void main(String[] args) {
        LFUCache cache = new LFUCache(2);
        cache.put(1, 1);
        cache.put(2, 2);
        System.out.println(cache.get(1));
        cache.put(3, 3);
        System.out.println(cache.get(2));
        System.out.println(cache.get(3));
        cache.put(4, 4);
        System.out.println(cache.get(1));
        System.out.println(cache.get(3));
        System.out.println(cache.get(4));
        System.out.println("////");

        LFUCache cache2 = new LFUCache(1);
        cache2.put(2, 1);
        System.out.println(cache2.get(1));
        System.out.println("////");

        LFUCache cache3 = new LFUCache(1);
        cache3.put(2, 1);
        System.out.println(cache3.get(2));
        cache3.put(3, 2);
        System.out.println(cache3.get(2));
        System.out.println(cache3.get(3));
    }
}
